/*----------------------------------------------------------------------------- 
GROUP:HoneyBadger
MEMBERS:Tyler Herzog, Chris Conry
DATE:February 2012
CS 4/55231 INTERNETENGINEERING 2012 SPRING
INSTRUCTOR: Javed Kahn
----------------------------------------------------------------------------- */

package server;

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.logging.FileHandler;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;

//Server class
//Implements Runnable to allow Thread manipulation/creation
public class Server implements Runnable {
	
	// Server variables

	ServerSocket listenSocket; // Our main socket, which listens for connections
	Socket connection; // This is the Socket which the connection runs through
	OutputStream outStream; // An output stream
	DataOutputStream outDataStream; // Stream used to send files and text
	BufferedReader inBuffer = null; // For reading in messages from client
	String message, inString = "."; // strings to contain messages
	File fileToSend; // File object to hold files needing sent
	ServerChild Child[] = new ServerChild[50];
	boolean run = true;
	int threadCount = 0;
	Logger logger = Logger.getLogger("server.Server");
	FileHandler fh;

	/*----------------------------------------------------------------------------- 
	 GROUP NAME: HoneyBadger
	 MODULE DEVELOPER 1. Tyler Herzog 2. Chris Conry
	 MODULE DESCRIPTION: 
	 Default constructor; Creates the listenSocket and establishes the 
	 fileHandler for the log.  
	 ----------------------------------------------------------------------------- */

	public Server(String name) {
		//////// Socket Creation Begin /////////
		try {
			listenSocket = new ServerSocket(8080);
			System.out.println("Server started ");
			InetAddress here = InetAddress.getLocalHost();
			String host = here.getHostAddress();
			System.out.println(" Hostname: " + host + "\n Port : "
					+ listenSocket.getLocalPort());
		} catch (IOException except) {
			except.printStackTrace();
		}
		
		try{
		fh = new FileHandler("serverfiles/HoneyLog.log", true);
		logger.addHandler(fh);
		SimpleFormatter formatter = new SimpleFormatter();
		fh.setFormatter(formatter);
		} catch (IOException e) {System.out.println("ERROR WITH LOG FILE"); e.printStackTrace();}
		/////////Socket Creation END //////////
	}

	/*----------------------------------------------------------------------------- 
	GROUP NAME: HoneyBadger
	MODULE DEVELOPER 1. Tyler Herzog 2. Chris Conry
	MODULE DESCRIPTION: 
	Main function. Creates a server object and starts running the server
	----------------------------------------------------------------------------- */
	public static void main(String[] args) {
		Server server = new Server("parent"); // new server object named parent
		server.run(); // runs our server object
	}

	/*----------------------------------------------------------------------------- 
	GROUP NAME: HoneyBadger
	MODULE DEVELOPER 1. Tyler Herzog 2. Chris Conry
	MODULE DESCRIPTION: 
	run() function. Begins the server thread. Begins listening with accept().
	When a new client wants to connect, the server starts a new ServerChild thread
	passing it the connection. 
	----------------------------------------------------------------------------- */
	public void run() {
		while (run) {
			try {
				connection = listenSocket.accept(); // connect the socket
				ClientID(connection);
				System.out.println("Starting thread " + threadCount + "\n");
				addThread(connection);
			} catch (IOException except) {
				System.out.println("IO exception found. debug it");
			}
		}
	}
	
	/*----------------------------------------------------------------------------- 
	GROUP NAME: HoneyBadger
	MODULE DEVELOPER 1. Tyler Herzog 2. Chris Conry
	MODULE DESCRIPTION: 
	Function that removes threads from the stack of them. Function is synchronized
	so only one thread can execute it at a time. If there is only one thread, simply
	sets the [0] index to null and decrements threadCount. Otherwise, it moves all
	threads later in the array down (toward 0) and sets last one to null. Also 
	reassigns the id # for each thread. 
	----------------------------------------------------------------------------- */
	public synchronized void removeThread(int id) {
		if(threadCount==1){ //only one thread present 
			Child[0]=null;
			System.out.println("Child 0: Removed");
			threadCount--; //reset thread count
		}
		else{ //more than one thread running
			//move all the threads back one slot
			for(int i=id; i<threadCount-1; i++){
				Child[i]=Child[i+1];
				Child[i].setID(i); //give the thread it's new ID
			}
			//set last thread to null and decrement count
			Child[--threadCount]=null; 
			System.out.println("Child-"+id+": Removed");
		}
	}
	/*----------------------------------------------------------------------------- 
	GROUP NAME: HoneyBadger
	MODULE DEVELOPER 1. Tyler Herzog 2. Chris Conry
	MODULE DESCRIPTION: 
	When a client connects to the server, the ServerChild thread handling it 
	calls this to print out the connection information on the CL 
	----------------------------------------------------------------------------- */
	public void ClientID(Socket sock) {
		System.out.println("Connection request\n HEX IP ADDRESS: 0x"
				+ java.lang.Integer.toHexString(ByteBuffer
						.wrap(sock.getInetAddress().getAddress())
						.order(ByteOrder.BIG_ENDIAN).getInt())
				+ "\n Dot-Decimal IP ADDRESS:" + sock.getInetAddress()
				+ "\n PORT:" + sock.getPort());
	}
	
	/*----------------------------------------------------------------------------- 
	GROUP NAME: HoneyBadger
	MODULE DEVELOPER 1. Tyler Herzog 2. Chris Conry
	MODULE DESCRIPTION: 
	When a client connects, this is called to add a new ServerChild thread and 
	appropriately handles the array. 50 threads at once is MAX. Also starts thread
	----------------------------------------------------------------------------- */
	private void addThread(Socket socket) {
		if (threadCount < 50) {
			Child[threadCount] = new ServerChild(this, threadCount, connection); 
			Child[threadCount++].start();

		} else
			System.out.println("Too many threads running at the same time, be patient");
	}
	
	/*----------------------------------------------------------------------------- 
	GROUP NAME: HoneyBadger
	MODULE DEVELOPER 1. Tyler Herzog 2. Chris Conry
	MODULE DESCRIPTION: 
	A function that handles a command in chat mode for clients. Simple walks through
	the array and creates an array of names for the current threads.
	----------------------------------------------------------------------------- */
	public String[] buddyList() {
		String[] names = new String[threadCount];
		for (int i = 0; i < threadCount; i++)
			names[i] = Child[i].getClientName();
		return names;
	}
	
	/*----------------------------------------------------------------------------- 
	GROUP NAME: HoneyBadger
	MODULE DEVELOPER 1. Tyler Herzog 2. Chris Conry
	MODULE DESCRIPTION: 
	Logs the passed strings
	----------------------------------------------------------------------------- */
	public void log (String str) {
		logger.info(str);
	}

	/*----------------------------------------------------------------------------- 
	GROUP NAME: HoneyBadger
	MODULE DEVELOPER 1. Tyler Herzog 2. Chris Conry
	MODULE DESCRIPTION: 
	This function is for chat mode. When a client in chat mode sends a message, this
	function sends the message to all connected clients. Also handles commands for 
	the buddy list, and quit.
	----------------------------------------------------------------------------- */
	public void broadcast(int id, String str) {
		if (str.equals("/quit")) {
			System.out.println(Child[id].getClientName()+ " is quitting chat.\n");
		}
		if (str.equals("/pals")) {
			// asking for a list of the names connected
			String[] nameList = buddyList();
			int len = nameList.length;
			Child[id].sendMessage("Available chatters: \n");
			for (int i = 0; i < len; i++)
				Child[id].sendMessage(nameList[i]);
		}

		else {
			// Send the message to all the clients!
			System.out.println("thread" + id + " sent a message in chat.\n");
			str = Child[id].getClientName() + ": " + str;
			for (int i = 0; i < threadCount; i++) {
				if(Child[i].isChatting())
					Child[i].sendMessage(str);
			}
		}
	}
}
